﻿using System;
using System.Collections.Generic;
using System.Windows.Media;

using Bling.Core;
using Bling.DSL;
using Bling.WPF;
using Bling.WPF.Custom;
using Bling.Graphics;
using Bling.DX;
using Bling.Matrices;
using Bling.WPF3D;
using Bling.Angles;

namespace Bling.Example {
  public class Intermediate : HasDX10Canvas {
    public Intermediate()
      // by passing true to base, an overlay window will be created.
      : base(true) {
      Host.Size = new Double2Bl(500, 500); // host size must be constant!
      Host.LeftTop = 0d;
      // background of overlay can't be transparent or it won't receive input. It can be a very transparent color though, go fig. 
      OverlayContent.Background = new SolidColorBrushBl() { 
        Color = Colors.Red, 
        Opacity = OverlayContent.IsMouseOver.Condition(.15d, .01d), 
      };
      // create a virtual trackpad to define Rotate.
      QuaternionBl Rotate;
      {
        // An ellipse to represent our virtual trackpad, only visible when the mouse is over the overlay
        EllipseBl sphere = new EllipseBl(OverlayContent) {
          CenterPosition = OverlayContent.CenterSize,
          Stroke = { Brush = Brushes.White, Thickness = 2d },
          Size = 400d,
          Visibility = OverlayContent.IsMouseOver,
          // so mouse events on the sphere are passed to overlay content instead
          IsHitTestVisible = false,
        };
        // rotation is an anonymous property that can undergo data binding.
        Rotate = this.Property(new QuaternionBl(1d.Bl().XXX(), .33.PI()));
        // compute virtual sphere mouse location according to mouse's 2D position
        Double2Bl MouseAt = sphere.Mouse.Position / sphere.Size;
        // screen cooridnates are [0,1] while 3D coordinates are [-1,1], use n * 2d - 1 to translate.
        MouseAt = MouseAt * 2d - 1d;
        // for some reason, X is flipped in result, easy enough to fix. 
        MouseAt = MouseAt * new Double2Bl(-1d, +1d);
        // determine Z value based on X and Y values, if x,y are outside of the sphere, simply use 0.
        Double3Bl MouseAt3D = new Double3Bl(MouseAt, (1d - MouseAt.LengthSquared).Max(0d).Sqrt);
        MouseAt3D = MouseAt3D.Normalize;

        // Rotation update occurs on mouse drag
        // prepend the angle between the current mouse and mouse at the beginning of the
        // dragging to the rotation at the beginning of the rotation. 
        Rotate[OverlayContent.Mouse.Drag.Update] =
          MouseAt3D.Normalize.AngleBetween(MouseAt3D[OverlayContent.Mouse.Drag.In]) *
          Rotate[OverlayContent.Mouse.Drag.In];
      }
      // A cube vertex topology, 4 vertices per face.
      CubeMesh Cube = new CubeMesh();

      //  create a perspective camera.
      Double3Bl WorldCenter = new Double3Bl(0, 0, 0);
      // camera position is used in lighting, so externalize
      Double3Bl CameraPosition = WorldCenter + new Double3Bl(0, 0, -4);
      //var Slider0 = new LabelSliderBl(this) { LabelName = "XX", Minimum = 0, Maximum = 2, Value = 0, LeftBottom = new PointBl(0d, this.Bl().Height) };
      CameraCl Camera = new PerspectiveCameraCl() {
        Position = CameraPosition,
        Direction = { Look = new Double3Bl(0, 0, 1), },
        //Size = (Slider0.Value)
      };
      int DIM = 10;
      Device.Render(Cube, DIM * DIM, (IntBl m, CubeMesh.Mesh3DEntry Entry, IVertex vertex) => {
        // n is the vertex index, there are four vertices per face, so divide by 4 to find the face id.
        //IntBl face = Entry.Index / 4;

        // quaternions for both x and y rotation, determined by sliders.
        // m is the instance index, there are 16 instances, arrange in a square configuration using %/ by 4.
        Double2Bl mxy = new Double2Bl(m % DIM, m / DIM);
        // the current coord is [0,1,2,3] based, we need to convert to [-1,+1] based.
        mxy = (mxy / ((DoubleBl)DIM.Bl()));
        mxy = mxy - .4;
        DoubleBl sz = 2d.Bl() / DIM.Bl();
        sz = sz / 4;

        // find the xyz coordinate of the current vertex index
        Double3Bl WorldPos = Entry.Position;
        WorldPos = ((Camera.Project * Camera.View) * Matrix4Bl.MakeTranslate(mxy.AddZ(0)) *
          Matrix4Bl.MakeScale(sz) * Rotate.Matrix).Transform(WorldPos);
        vertex.Position = WorldPos;

        ColorBl[] FaceColors = new ColorBl[] {
          Colors.Red,
          Colors.LightGreen,
          Colors.Blue,
          Colors.Yellow,
          Colors.LightPink,
          Colors.Salmon,
        };
        ColorBl clr = FaceColors.Table(Entry.Face % FaceColors.Length);
        // The normal comes from the cube topology, but must be transformed according to the current rotation.
        Double3Bl normal = Rotate.Matrix.Transform(Entry.Normal);
        // create a slider to control spot light direction.
        Double2Bl LightPosition = this.Property(new Double2Bl(0, 0));
        {
          OverlayContent.Mouse.HoldTimeout = TimeSpan.FromMilliseconds(300);
          Double2Bl UseLightPosition = OverlayContent.Mouse.Position / OverlayContent.Size;
          UseLightPosition = UseLightPosition * 2d - 1d;
          UseLightPosition = UseLightPosition * .1 * new Double2Bl(+1,-1);
          LightPosition[OverlayContent.Mouse.Hold.Update] = 
            LightPosition[OverlayContent.Mouse.Hold.In] + 
            (UseLightPosition - UseLightPosition[OverlayContent.Mouse.Hold.In]);
        }
        // illuminate the cubes with a point light on a diffuse material.
        LightBl light = new SpotLightBl() {
          Direction = (new Double3Bl(LightPosition, +1)).Normalize,
          Color = Colors.White,
          Position = new Double3Bl(0, 0, -2.5),
          InnerConeAngle = 2.Degrees(),
          OuterConeAngle = 4.Degrees(),
        };
        MaterialCl diffuse = new DiffuseMaterialCl() {
          // how much non-ambient light is relfected
          Knob = .5,
          Ambient = {
            // how much ambient light is reflected
            Knob = .5
          }
        };
        // combine light with diffuse material via apply, plugin in world position, normal, and camera position, then color. 
        // also, compose an ambient light so nothing is completely dark.
        // Note: 
        // Use WorldPos.PerPixel() rather than WorldPos for better per-pixel lighting computations.
        // If efficiency is an issue, use WorldPos instead for cheaper per-vertex lighting computations.
        clr = diffuse.ApplyLights(light + new AmbientLightBl() { Color = Colors.White }).
          ApplyAt(WorldPos.PerPixel(), normal, CameraPosition).ForColor(clr);
        vertex.Color = clr;
      });
    }
  }
}
